package alibaba

import (
	"os"
	"testing"

	"github.com/stretchr/testify/require"

	"golang.aliyun-inc.com/agit/agit/proc-receive-hook/protocol"
)

func Test_resolveCommand(t *testing.T) {
	for _, tc := range []struct {
		desc           string
		command        *protocol.Command
		expectedParmas AGitFlowParams
		ref            string
		wantErr        bool
	}{
		{
			desc:    "invalid ref format",
			ref:     "refs/for",
			wantErr: true,
		},
		{
			desc: "create normal MR",
			ref:  "refs/for/master",
			expectedParmas: AGitFlowParams{
				Action:  "create",
				IsDraft: false,
			},
		},
		{
			desc: "create draft MR",
			ref:  "refs/drafts/master",
			expectedParmas: AGitFlowParams{
				Action:  "create",
				IsDraft: true,
			},
		},
		{
			desc: "update MR",
			ref:  "refs/for-review/master%1234",
			expectedParmas: AGitFlowParams{
				Action: "update",
				ID:     "1234",
			},
		},
		{
			desc:    "invalid agitflow ref",
			ref:     "refs/dyrone/teng",
			wantErr: true,
		},
	} {
		t.Run(tc.desc, func(t *testing.T) {
			params := &AGitFlowParams{}
			command := &protocol.Command{
				GitCommand: protocol.GitCommand{
					Ref: tc.ref,
				},
			}
			err := params.resolveCommand(command)
			if tc.wantErr {
				require.Error(t, err)
				return
			}
			require.NoError(t, err)
			require.Equal(t, tc.expectedParmas.Action, params.Action)
			require.Equal(t, tc.expectedParmas.IsDraft, params.IsDraft)
		})
	}
}

func Test_resolvePushOptions(t *testing.T) {
	Des := "dyrone description"
	Rev := "dyrone,jiangxin,hanxin"
	CC := "dyrone"
	for _, tc := range []struct {
		desc                string
		opts                []protocol.PushOption
		expectedParams      AGitFlowParams
		expectedDescription string
		wantErr             bool
	}{
		{
			desc: "full push-options",
			opts: []protocol.PushOption{
				{Name: "proc-receive-debug", Value: "1"},
				{Name: "title", Value: "dyrone title"},
				{Name: "description", Value: "dyrone description"},
				{Name: "reviewer", Value: "dyrone"},
				{Name: "reviewers", Value: "jiangxin"},
				{Name: "re", Value: "hanxin"},
				{Name: "cc", Value: "dyrone"},
				{Name: "issue", Value: "issue1"},
				{Name: "private", Value: "yes"},
				{Name: "wip", Value: "yes"},
				{Name: "notify", Value: "no"},
				{Name: "oldoid", Value: "daa4cb4774f7b39ebcd1176491680d98df82df1a"},
			},
			expectedDescription: Des,
			expectedParams: AGitFlowParams{
				verbose:   true,
				Title:     "dyrone title",
				Reviewers: &Rev,
				CCs:       &CC,
				Issues:    "issue1",
				IsPrivate: true,
				IsWIP:     true,
				OldOID:    "daa4cb4774f7b39ebcd1176491680d98df82df1a",
			},
		},
		{
			desc: "multi-line message",
			opts: []protocol.PushOption{
				{Name: "message", Value: "dyrone title\ndyrone description"},
			},
			expectedDescription: Des,
			expectedParams: AGitFlowParams{
				Title: "dyrone title",
			},
		},
		{
			desc: "single-line message",
			opts: []protocol.PushOption{
				{Name: "message", Value: "dyrone title"},
			},
			expectedParams: AGitFlowParams{
				Title: "dyrone title",
			},
		},
		{
			desc: "base64 encoded single-line message",
			opts: []protocol.PushOption{
				{Name: "message", Value: "{base64}ZHlyb25lIHRpdGxl"},
			},
			expectedParams: AGitFlowParams{
				Title: "dyrone title",
			},
		},
		{
			desc: "invalid base64 encoded reviewer",
			opts: []protocol.PushOption{
				{Name: "re", Value: "moweng.xx"},
				{Name: "re", Value: "{base64}err"},
				{Name: "re", Value: "dyrone"},
			},
			wantErr: true,
		},
	} {
		t.Run(tc.desc, func(t *testing.T) {
			params := &AGitFlowParams{Description: new(string)}
			err := params.resolvePushOptions(tc.opts)
			if tc.wantErr {
				require.Error(t, err)
				return
			}
			require.NoError(t, err)

			require.Equal(t, tc.expectedParams.verbose, params.verbose)
			require.Equal(t, tc.expectedParams.Title, params.Title)
			require.Equal(t, tc.expectedDescription, *params.Description)
			require.Equal(t, tc.expectedParams.Reviewers, params.Reviewers)
			require.Equal(t, tc.expectedParams.CCs, params.CCs)
			require.Equal(t, tc.expectedParams.Issues, params.Issues)
			require.Equal(t, tc.expectedParams.IsPrivate, params.IsPrivate)
			require.Equal(t, tc.expectedParams.IsWIP, params.IsWIP)
			require.Equal(t, tc.expectedParams.IsNotify, params.IsNotify)
			require.Equal(t, tc.expectedParams.OldOID, params.OldOID)
		})
	}
}

func Test_resolveRefOptions(t *testing.T) {
	for _, tc := range []struct {
		desc              string
		ref               string
		expectedParams    AGitFlowParams
		expectDescription string
		expectReviewers   string
		expectCCs         string
		wantErr           bool
	}{
		{
			desc:           "no ref options",
			ref:            "refs/for/master",
			expectedParams: AGitFlowParams{},
		},
		{
			desc:            "with multi 'r=' ref options",
			ref:             "refs/for/master%r=dyrone,r=jiangxin",
			expectedParams:  AGitFlowParams{},
			expectReviewers: "dyrone,jiangxin",
		},
		{
			desc:            "with single 'r=' ref options",
			ref:             "refs/for/master%r=dyrone",
			expectedParams:  AGitFlowParams{},
			expectReviewers: "dyrone",
		},
		{
			desc:           "with multi 'cc=' ref options",
			ref:            "refs/for/master%cc=dyrone,cc=jiangxin",
			expectedParams: AGitFlowParams{},
			expectCCs:      "dyrone,jiangxin",
		},
		{
			desc:           "with single 'cc=' ref options",
			ref:            "refs/for/master%cc=dyrone",
			expectedParams: AGitFlowParams{},
			expectCCs:      "dyrone",
		},
		{
			desc:           "with private ref options",
			ref:            "refs/for/master%private",
			expectedParams: AGitFlowParams{IsPrivate: true},
		},
		{
			desc:           "with wip ref options",
			ref:            "refs/for/master%wip",
			expectedParams: AGitFlowParams{IsWIP: true},
		},
		{
			desc:           "with notify ref options",
			ref:            "refs/for/master%notify=NONE",
			expectedParams: AGitFlowParams{IsNotify: false},
		},
		{
			desc:           "with topic ref options",
			ref:            "refs/for/master%topic=test",
			expectedParams: AGitFlowParams{SourceBranch: "test"},
		},
		{
			desc:           "with old oid ref options",
			ref:            "refs/for/master%oldoid=d3616d22e791379b9ee59d81c2a74eaf887909d4",
			expectedParams: AGitFlowParams{OldOID: "d3616d22e791379b9ee59d81c2a74eaf887909d4"},
		},
		{
			desc:           "with title ref options",
			ref:            "refs/for/master%title=一起打造云上代码基础设施",
			expectedParams: AGitFlowParams{Title: "一起打造云上代码基础设施"},
		},
		{
			desc:              "with description ref options",
			ref:               "refs/for/master%description=云原生+GAAS+SAAS",
			expectedParams:    AGitFlowParams{},
			expectDescription: "云原生+GAAS+SAAS",
		},
		{
			desc:           "with verbose logging",
			ref:            "refs/for/master%proc-receive-debug",
			expectedParams: AGitFlowParams{verbose: true},
		},
		{
			desc: "with full ref options",
			ref:  "refs/for/master%refoptionall,r=dyrone,r=jiangxin,cc=dyrone,cc=jiangxin,private,wip,notify=NONE,topic=test,oldoid=d3616d22e791379b9ee59d81c2a74eaf887909d4,title=一起打造云上代码基础设施,description=云原生+GAAS+SAAS,issue=issue01,issue=issue02",
			expectedParams: AGitFlowParams{
				Title:        "一起打造云上代码基础设施",
				IsPrivate:    true,
				IsWIP:        true,
				IsNotify:     false,
				SourceBranch: "test",
				OldOID:       "d3616d22e791379b9ee59d81c2a74eaf887909d4",
				Issues:       "issue01,issue02",
			},
			expectCCs:         "dyrone,jiangxin",
			expectDescription: "云原生+GAAS+SAAS",
			expectReviewers:   "dyrone,jiangxin",
		},
	} {
		t.Run(tc.desc, func(t *testing.T) {
			params := &AGitFlowParams{Description: new(string)}
			err := params.resolveRefOptions(tc.ref)
			if tc.wantErr {
				require.Error(t, err)
				return
			}
			require.NoError(t, err)

			require.Equal(t, tc.expectedParams.verbose, params.verbose)
			require.Equal(t, tc.expectedParams.Title, params.Title)
			require.Equal(t, tc.expectDescription, *params.Description)
			if params.Reviewers != nil {
				require.Equal(t, tc.expectReviewers, *params.Reviewers)
			}
			if params.CCs != nil {
				require.Equal(t, tc.expectCCs, *params.CCs)
			}
			require.Equal(t, tc.expectedParams.IsPrivate, params.IsPrivate)
			require.Equal(t, tc.expectedParams.IsWIP, params.IsWIP)
			require.Equal(t, tc.expectedParams.IsNotify, params.IsNotify)
			require.Equal(t, tc.expectedParams.SourceBranch, params.SourceBranch)
			require.Equal(t, tc.expectedParams.OldOID, params.OldOID)
			require.Equal(t, tc.expectedParams.Issues, params.Issues)
		})
	}
}

func Test_resolveEnv(t *testing.T) {
	type envPair struct{ key, val string }
	for _, tc := range []struct {
		desc           string
		envs           []envPair
		expectedParams AGitFlowParams
		wantErr        bool
	}{
		{
			desc: "ok envs with a user id",
			envs: []envPair{
				{key: "GL_ID", val: "user-1"},
				{key: "GL_REPOSITORY", val: "path/to/demo"},
				{key: "GL_REPOSITORY_ID", val: "10000"},
				{key: "GL_USERNAME", val: "zhangsan"},
			},
			expectedParams: AGitFlowParams{
				UserID:     "1",
				Repository: "path/to/demo",
				ProjectID:  "10000",
				GlUsername: "zhangsan",
			},
		},
		{
			desc: "ok envs with a key id",
			envs: []envPair{
				{key: "GL_ID", val: "key-1"},
				{key: "GL_REPOSITORY", val: "path/to/demo"},
				{key: "GL_REPOSITORY_ID", val: "10000"},
				{key: "GL_USERNAME", val: "zhangsan"},
			},
			expectedParams: AGitFlowParams{
				KeyID:      "1",
				Repository: "path/to/demo",
				ProjectID:  "10000",
				GlUsername: "zhangsan",
			},
		},
		{
			desc: "invalid gl_id type",
			envs: []envPair{
				{key: "GL_ID", val: "invalid-1"},
				{key: "GL_REPOSITORY", val: "path/to/demo"},
				{key: "GL_REPOSITORY_ID", val: "10000"},
				{key: "GL_USERNAME", val: "zhangsan"},
			},
			wantErr: true,
		},
		{
			desc: "invalid gl_id format",
			envs: []envPair{
				{key: "GL_ID", val: "10086"},
				{key: "GL_REPOSITORY", val: "path/to/demo"},
				{key: "GL_REPOSITORY_ID", val: "10000"},
				{key: "GL_USERNAME", val: "zhangsan"},
			},
			wantErr: true,
		},
	} {
		t.Run(tc.desc, func(t *testing.T) {
			params := &AGitFlowParams{}
			for _, env := range tc.envs {
				env := env
				require.NoError(t, os.Setenv(env.key, env.val))
				defer func() {
					require.NoError(t, os.Unsetenv(env.key))
				}()
			}

			err := params.resolveEnv()
			if tc.wantErr {
				require.Error(t, err)
				return
			}

			require.NoError(t, err)
			require.Equal(t, tc.expectedParams.KeyID, params.KeyID)
			require.Equal(t, tc.expectedParams.UserID, params.UserID)
			require.Equal(t, tc.expectedParams.Repository, params.Repository)
			require.Equal(t, tc.expectedParams.ProjectID, params.ProjectID)
			require.Equal(t, tc.expectedParams.GlUsername, params.GlUsername)
		})
	}
}

func Test_resolveSourceAndTargetBranch(t *testing.T) {
	// TODO(moweng.xx): this test must be run on master branch, which is weird
	for _, tc := range []struct {
		desc           string
		ref            string
		expectedParams AGitFlowParams
		wantErr        bool
	}{
		{
			desc: "without source branch",
			ref:  "refs/for/master",
			expectedParams: AGitFlowParams{
				TargetBranch: "master",
			},
		},
		{
			desc: "with source branch",
			ref:  "refs/for/master/this/is/source/branch",
			expectedParams: AGitFlowParams{
				TargetBranch: "master",
				SourceBranch: "this/is/source/branch",
			},
		},
		{
			desc: "with source branch and ref options",
			ref:  "refs/for/master/this/is/source/branch%cc=moweng.xx",
			expectedParams: AGitFlowParams{
				TargetBranch: "master",
				SourceBranch: "this/is/source/branch",
			},
		},
		{
			desc:    "invalid target branch",
			ref:     "refs/for/branch/not/exists",
			wantErr: true,
		},
	} {
		t.Run(tc.desc, func(t *testing.T) {
			params := &AGitFlowParams{}
			err := params.resolveSourceAndTargetBranch(tc.ref)
			if tc.wantErr {
				require.Error(t, err)
				return
			}
			require.NoError(t, err)

			require.Equal(t, tc.expectedParams.SourceBranch, params.SourceBranch)
			require.Equal(t, tc.expectedParams.TargetBranch, params.TargetBranch)
		})
	}
}
